<?php

namespace W3;

/**
 * 框架插件类
 *
 * @author edikud
 * @date 2022/10/22
 * @copyright Copyright (c) 2022 W3 (http://www.mcooo.com)
 * @license GNU General Public License 2.0
 */
class Plugin
{
    /**
     * 当前启用插件数组
     * @var []
     */
    protected static $plugins = [];
	
    /**
     * 插件目录路径
     * @var string
     */
    protected static $pluginDir;
	
    /**
     * 初始化
     *
     * @access public
     * @param string $plugins 当前启用插件数组
     * @return void
     */
    public static function init($plugins)
    {
        static::$plugins = $plugins;
		
		# 执行插件应用
		static::execute();
    }
	
    /**
     * 判断插件是否存在
     *
     * @access public
     * @param string $pluginName 插件名称
     * @return mixed
     */
    public static function exists($pluginName) 
	{
        return isset(static::$plugins[$pluginName]);
    }
	
    /**
     * 启用插件
     *
     * @access public
     * @param string $pluginName 插件名称
     * @return void
     */
    public static function activate($pluginName)
    {
        static::$plugins[$pluginName] = $pluginName;
    }
	
    /**
     * 禁用插件
     *
     * @access public
     * @param string $pluginName 插件名称
     * @return void
     */
    public static function deactivate($pluginName)
    {
		static::$plugins[$pluginName] = NULL;
		unset(static::$plugins[$pluginName]);
    }
	
    /**
     * 导出当前插件设置
     *
     * @access public
     * @return array
     */
    public static function export()
    {
        return static::$plugins;
    }
	
    /**
     * 获取插件类名
     *
     * @access public
     * @param string $pluginName 插件名
     * @return string
     */
    public static function getName($pluginName)
    {
        return "Plugin\\$pluginName\\Plugin";
    }
	
    /**
     * 获取插件路径
     *
     * @access public
     * @param string $pluginName 插件名
     * @return string
     */
    public static function getFile($pluginName)
    {
		$file = NULL;
        if(file_exists($pluginFile = W3_PLUGIN_DIR . $pluginName . DIRECTORY_SEPARATOR . 'Plugin.php')){
			$file = $pluginFile;
        }

        return $file;
    }

    /**
     * 执行插件应用
     *
     * @access public
     * @return array
     */
    public static function execute()
    {
        foreach (static::$plugins as $pluginName) 
		{
			$className = static::getName($pluginName);
			$file = static::getFile($pluginName);
			
			if(NULL !== $file){
				
				include $file;
				$className::execute();
			}
		}
    }
	
    /**
     * 版本依赖性检测
     *
     * @access public
     * @param string $version 程序版本
     * @param string $versionRange 依赖的版本规则
     * @return boolean
     */
    public static function version($version, $versionRange)
    {
        //如果没有检测规则,直接掠过
        if (empty($versionRange)) {
            return true;
        }

        $items = array_map('trim', explode('-', $versionRange));
        if (count($items) < 2) {
            $items[1] = '9999.9999.9999';
        }

        list ($minVersion, $maxVersion) = $items;

        //对*和?的支持,4个9是最大版本
        $minVersion = str_replace(['*', '?'], ['9999', '9'], $minVersion);
        $maxVersion = str_replace(['*', '?'], ['9999', '9'], $maxVersion);

        if (version_compare($version, $minVersion, '>=') && version_compare($version, $maxVersion, '<=')) {
            return true;
        }

        return false;
    }
	

    /**
     * 获取文件的头信息
     *
     * @access public
     * @param string $file 文件路径
     * @return array
     */
    public static function getInfo(string $pluginFile): array
    {
        $tokens = token_get_all(file_get_contents($pluginFile));
        $isDoc = false;
        $isFunction = false;
        $isClass = false;
        $isInClass = false;
        $isInFunction = false;
        $isDefined = false;
        $current = null;

        /** 初始信息 */
        $info = [
            'description' => '',
            'title' => '',
            'author' => '',
            'homepage' => '',
            'version' => '',
            'dependence' => '',
            'activate' => false,
            'deactivate' => false,
            'config' => false
        ];

        $map = [
            'package' => 'title',
            'author' => 'author',
            'link' => 'homepage',
            'dependence' => 'dependence',
            'version' => 'version'
        ];

        foreach ($tokens as $token) {
            /** 获取doc comment */
            if (!$isDoc && is_array($token) && T_DOC_COMMENT == $token[0]) {

                /** 分行读取 */
                $described = false;
                $lines = preg_split("(\r|\n)", $token[1]);
                foreach ($lines as $line) {
                    $line = trim($line);
                    if (!empty($line) && '*' == $line[0]) {
                        $line = trim(substr($line, 1));
                        if (!$described && !empty($line) && '@' == $line[0]) {
                            $described = true;
                        }

                        if (!$described && !empty($line)) {
                            $info['description'] .= $line . "\n";
                        } else if ($described && !empty($line) && '@' == $line[0]) {
                            $info['description'] = trim($info['description']);
                            $line = trim(substr($line, 1));
                            $args = explode(' ', $line);
                            $key = array_shift($args);

                            if (isset($map[$key])) {
                                $info[$map[$key]] = trim(implode(' ', $args));
                            }
                        }
                    }
                }

                $isDoc = true;
            }

            if (is_array($token)) {
                switch ($token[0]) {
                    case T_FUNCTION:
                        $isFunction = true;
                        break;
                    case T_IMPLEMENTS:
                        $isClass = true;
                        break;
                    case T_WHITESPACE:
                    case T_COMMENT:
                    case T_DOC_COMMENT:
                        break;
                    case T_STRING:
                        $string = strtolower($token[1]);
                        switch ($string) {
                            case 'plugins':
                                $isInClass = $isClass;
                                break;
                            case 'activate':
                            case 'deactivate':
                            case 'config':
                                if ($isFunction) {
                                    $current = $string;
                                }
                                break;
                            default:
                                if (!empty($current) && $isInFunction && $isInClass) {
                                    $info[$current] = true;
                                }
                                break;
                        }
                        break;
                    default:
                        if (!empty($current) && $isInFunction && $isInClass) {
                            $info[$current] = true;
                        }
                        break;
                }
            } else {
                $token = strtolower($token);
                switch ($token) {
                    case '{':
                        if ($isDefined) {
                            $isInFunction = true;
                        }
                        break;
                    case '(':
                        if ($isFunction && !$isDefined) {
                            $isDefined = true;
                        }
                        break;
                    case '}':
                    case ';':
                        $isDefined = false;
                        $isFunction = false;
                        $isInFunction = false;
                        $current = null;
                        break;
                    default:
                        if (!empty($current) && $isInFunction && $isInClass) {
                            $info[$current] = true;
                        }
                        break;
                }
            }
        }

        return $info;
    }
}
